/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "movement_manager.h"
#include <iostream>

#include "movement_msdp_client_impl.h"

using namespace OHOS::NativeRdb;

namespace OHOS {
namespace Msdp {
const std::string DATABASE_NAME = "/data/MsdpStub.db";
const std::string CREATE_TABLE = std::string("CREATE TABLE IF NOT EXISTS OBSERVER ") +
    std::string("(ID INTEGER PRIMARY KEY AUTOINCREMENT, MOVEMENT_TYPE INTEGER, MOVEMENT_STATUS INTEGER)");
void MovementManager::MovementCallbackDeathRecipient::OnRemoteDied(const wptr<IRemoteObject>& remote)
{
    if (remote == nullptr) {
        MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "OnRemoteDied failed, remote is nullptr");
        return;
    }
    MOVEMENT_HILOGD(MOVEMENT_MODULE_SERVICE, "Recv death notice");
}

bool MovementManager::Init()
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "MovementManager: Init start");
    if (movementCBDeathRecipient_ == nullptr) {
        movementCBDeathRecipient_ = new MovementCallbackDeathRecipient();
    }

    msdpImpl_ = std::make_unique<MovementMsdpClientImpl>();
    if (msdpImpl_ == nullptr) {
        return false;
    }
    LoadAlgorithm(false);

    if (!InitInterface()) {
        return false;
    }

    if (!InitDataCallback()) {
        MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "%{public}s init msdp callback fail.", __func__);
        return false;
    }

    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "MovementManager: Init success");
    return true;
}

MovementDataUtils::MovementData MovementManager::GetLatestMovementData(const MovementDataUtils::MovementType& type)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    MovementDataUtils::MovementData data = {type, MovementDataUtils::MovementValue::VALUE_EXIT};

    msdpData_ = msdpImpl_->GetObserverData();
    for (auto iter = msdpData_.begin(); iter != msdpData_.end(); ++iter) {
        if (data.type == iter->first) {
            data.value = iter->second;
            return data;
        }
    }

    data.value = MovementDataUtils::MovementValue::VALUE_INVALID;
    return data;
}

bool MovementManager::InitInterface()
{
    MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "enter");
    if (msdpImpl_ != nullptr) {
        MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "Init msdp client impl");
        msdpImpl_->InitMsdpImpl();
    }
    return true;
}

bool MovementManager::InitDataCallback()
{
    MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "enter");
    if (msdpImpl_ != nullptr) {
        MovementMsdpClientImpl::CallbackManager callback =
            std::bind(&MovementManager::MsdpDataCallback, this, std::placeholders::_1);
        msdpImpl_->RegisterImpl(callback);
        MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "InitDataCallback: after register impl");
    }
    return true;
}

int32_t MovementManager::WriteObserverDataToDatabase(MovementDataUtils::MovementData& data)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    int errCode = ERR_OK;
    int ret = -1;
    int64_t ID;

    RdbStoreConfig config(DATABASE_NAME);
    InsertCallback helper;
    if (store_ == nullptr) {
        store_ = RdbHelper::GetRdbStore(config, 1, helper, errCode);
    }

    ret = store_->ExecuteSql(CREATE_TABLE);
    if (ret != ERR_OK) {
        MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "failed to create table");
        return ret;
    }

    ValuesBucket values;
    values.PutInt("ID", primaryKey_);
    values.PutInt("MOVEMENT_TYPE", data.type);
    values.PutInt("MOVEMENT_STATUS", data.value);
    ret = store_->Insert(ID, "OBSERVER", values);
    if (ret != ERR_OK) {
        MOVEMENT_HILOGE(MOVEMENT_MODULE_SERVICE, "failed to insert data");
        return ret;
    }

    primaryKey_++;
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
    return ERR_OK;
}

int32_t MovementManager::MsdpDataCallback(MovementDataUtils::MovementData& data)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    WriteObserverDataToDatabase(data);

    NotifyMovementChange(data);
    return ERR_OK;
}

void MovementManager::NotifyMovementChange(const MovementDataUtils::MovementData& movementData)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");

    // Call back for all listeners
    std::set<const sptr<ImovementCallback>, classcomp> listeners;
    bool isExists = false;
    for (auto it = listenerMap_.begin(); it != listenerMap_.end(); ++it) {
        if (it->first == movementData.type) {
            isExists = true;
            listeners = (std::set<const sptr<ImovementCallback>, classcomp>)(it->second);
            break;
        }
    }
    if (!isExists) {
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "No listener found for type: %{public}d", movementData.type);
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
        return;
    }
    for (auto& listener : listeners) {
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Found callback: %{public}p", listener.GetRefPtr());
        listener->OnMovementChanged(movementData);
    }
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
}

void MovementManager::Subscribe(const MovementDataUtils::MovementType& type,
    const sptr<ImovementCallback>& callback)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    MOVEMENT_RETURN_IF(callback == nullptr);
    auto object = callback->AsObject();
    MOVEMENT_RETURN_IF(object == nullptr);
    std::set<const sptr<ImovementCallback>, classcomp> listeners;
    bool isExists = false;
    std::lock_guard lock(mutex_);
    for (auto it = listenerMap_.begin(); it != listenerMap_.end(); ++it) {
        if (it->first == type) {
            isExists = true;
            listeners = (std::set<const sptr<ImovementCallback>, classcomp>)(it->second);
            break;
        }
    }

    auto retIt = listeners.insert(callback);
    if (retIt.second) {
        object->AddDeathRecipient(movementCBDeathRecipient_);
    }

    if (!isExists) {
        std::pair<MovementDataUtils::MovementType, std::set<const sptr<ImovementCallback>, classcomp>> pair =
            std::make_pair(type, listeners);
        listenerMap_.insert(pair);
    }

    for (auto& listener : listeners) {
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "listener: %{public}p", listener.GetRefPtr());
    }

    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "type = %{public}d, object = %{public}p, callback = %{public}p",
        type, object.GetRefPtr(), callback.GetRefPtr());
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "listeners.size = %{public}d, insertOk = %{public}d",
        static_cast<unsigned int>(listeners.size()), retIt.second);
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
}

void MovementManager::UnSubscribe(const MovementDataUtils::MovementType& type, const sptr<ImovementCallback>& callback)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    std::lock_guard lock(mutex_);
    MOVEMENT_RETURN_IF(callback == nullptr);
    auto object = callback->AsObject();
    MOVEMENT_RETURN_IF(object == nullptr);
    std::set<const sptr<ImovementCallback>, classcomp> listeners;
    bool isExists = false;
    for (auto it = listenerMap_.begin(); it != listenerMap_.end(); ++it) {
        if (it->first == type) {
            isExists = true;
            listeners = (std::set<const sptr<ImovementCallback>, classcomp>)(it->second);
            break;
        }
    }
    if (!isExists) {
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "No listener found for type: %{public}d", type);
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
        return;
    }

    for (auto& listener : listeners) {
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "listener: %{public}p", listener.GetRefPtr());
        MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "listener_obj: %{public}p", listener->AsObject().GetRefPtr());
    }

    size_t eraseNum = listeners.erase(callback);
    if (eraseNum != 0) {
        object->RemoveDeathRecipient(movementCBDeathRecipient_);
    }
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "type = %{public}d, object = %{public}p, callback = %{public}p",
        type, object.GetRefPtr(), callback.GetRefPtr());
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "listeners.size = %{public}d, eraseNum = %{public}zu",
        static_cast<unsigned int>(listeners.size()), eraseNum);
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
}

int32_t MovementManager::SensorDataCallback(const struct SensorEvents *event)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    // TO-DO, handle sensor event properly when we get the data details of sensor HDI.
    MovementDataUtils::MovementData data = {MovementDataUtils::MovementType::TYPE_ON_FOOT,
        MovementDataUtils::MovementValue::VALUE_ENTER};
    NotifyMovementChange(data);
    return ERR_OK;
}

int32_t MovementManager::LoadAlgorithm(bool bCreate)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    if (msdpImpl_ != nullptr) {
        msdpImpl_->LoadAlgorithmLibrary(bCreate);
        msdpImpl_->LoadSensorHdiLibrary(bCreate);
    }

    return ERR_OK;
}

int32_t MovementManager::UnloadAlgorithm(bool bCreate)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Enter");
    if (msdpImpl_ != nullptr) {
        msdpImpl_->UnloadAlgorithmLibrary(bCreate);
        msdpImpl_->UnloadSensorHdiLibrary(bCreate);
    }

    return ERR_OK;
}

int InsertCallback::OnCreate(RdbStore &store)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "enter");

    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
    return ERR_OK;
}

int InsertCallback::OnUpgrade(RdbStore &store, int oldVersion, int newVersion)
{
    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "enter");

    MOVEMENT_HILOGI(MOVEMENT_MODULE_SERVICE, "Exit");
    return ERR_OK;
}
} // namespace Msdp
} // namespace OHOS
